
#include "sock-inet.hxx"

#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstdio>
#include <netdb.h>
#include <stdexcept>
#include <string>

using std::runtime_error;
using std::string;

////////////////////////////////////////////////////////////////////////////////

sock_inet::sock_inet(int sfd)
    : sfd(sfd) {
}

sock_inet::sock_inet(sock_inet&& rhs)
    : sfd(rhs.sfd) {
    rhs.sfd = -1;
}

sock_inet::sock_inet(int type, int protocol) {
    if((sfd = socket(AF_INET, type, protocol)) < 0)     {
        throw runtime_error("socket"); // TODO: specific exception
    }
}

sock_inet::~sock_inet() {
    shutdown();
}

sock_inet::address sock_inet::get_local() const {
    sock_inet::address addr(0, 0);
    socklen_t addrlen = sizeof(sockaddr);

    if(getsockname(sfd, (sockaddr*)&addr.get_sockaddr(), &addrlen) < 0) {
        throw runtime_error("getsockname"); // TODO: specific exception
    }

    return addr;
}

int sock_inet::get_fd() const {
    return sfd;
}

void sock_inet::shutdown() {
    if(sfd != -1) {
        ::shutdown(sfd, SHUT_RDWR);
    }
}

bool sock_inet::wait(const timeout& t) {
    fd_set rfds;
    timeval tv;

    FD_ZERO(&rfds);
    FD_SET(sfd, &rfds);

    tv.tv_sec = (time_t)(t.get_usec() / 1000000L);
    tv.tv_usec = (suseconds_t)(t.get_usec() % 1000000L);

    int r = select(sfd+1, &rfds, NULL, NULL, &tv);

    if(r < 0)     {
        throw runtime_error("select"); // TODO: specific exception
    }

    return r > 0;
}

void sock_inet::bind(const address& a) {
    if(::bind(sfd, &a.get_sockaddr(), (int)sizeof(sockaddr)) < 0)     {
        throw runtime_error("bind"); // TODO: specific exception
    }
}

////////////////////////////////////////////////////////////////////////////////

sock_inet::address::address(uint32_t host, uint16_t service) {
    ((sockaddr_in*)&addr)->sin_family = AF_INET;
    ((sockaddr_in*)&addr)->sin_addr.s_addr = htonl(host);
    ((sockaddr_in*)&addr)->sin_port = htons(service);
}

sock_inet::address::address(const string& host, const string& service) {
    addrinfo hints = { 0 };
    addrinfo *addr;

    hints.ai_family = AF_INET;

    if(getaddrinfo(host.c_str(), service.c_str(), &hints, &addr))
    {
        throw runtime_error("getaddrinfo"); // TODO: specific exception
    }

    this->addr = *addr->ai_addr;

    freeaddrinfo(addr);
}

bool sock_inet::address::operator==(const address& lhs) const {
    return (*(uint64_t*)&addr) == (*(uint64_t*)&lhs.addr);
}

sock_inet::address::operator string() const {
    uint32_t host = get_host();
    uint16_t service = get_service();
    char name[32];

    sprintf(name, "%d.%d.%d.%d:%d",
        ((host & 0xFF000000) >> 24),
        ((host & 0x00FF0000) >> 16),
        ((host & 0x0000FF00) >> 8),
        ((host & 0x000000FF) >> 0),
        service);

    return name;
}
uint32_t sock_inet::address::get_host() const {
    return ntohl(((sockaddr_in*)&addr)->sin_addr.s_addr);
}

uint16_t sock_inet::address::get_service() const {
    return ntohs(((sockaddr_in*)&addr)->sin_port);
}

const sockaddr& sock_inet::address::get_sockaddr() const {
    return addr;
}

sockaddr& sock_inet::address::get_sockaddr() {
    return addr;
}

////////////////////////////////////////////////////////////////////////////////

sock_inet::timeout::timeout(int64_t usec)
    : usec(usec) {
}

int64_t sock_inet::timeout::get_usec() const {
    return usec;
}

////////////////////////////////////////////////////////////////////////////////
